package com.ofek.db;

import java.lang.reflect.Field;
import java.util.ArrayList;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;

import com.ofek.base.db.Column;
import com.ofek.base.db.DataField;
import com.ofek.base.db.DataTypeClass;


public class Table<Item> {



	private String init(final Class<Item> clazz) {
		final StringBuilder columns = new StringBuilder();
		tableName = clazz.getSimpleName();
		final StringBuilder keys = new StringBuilder();
		int keysCount = 0;
		final Field[] fields = clazz.getDeclaredFields();
		for (final Field f : fields)
			if (f.isAnnotationPresent(DataField.class)) {
				final Column column = new Column(f);
				if (column.isKey())
					keysCount++;
			}
		if (keysCount > 1) {
			for (final Field f : fields)
				if (f.isAnnotationPresent(DataField.class)) {
					final Column column = new Column(f);
					columns.append(column);
					columns.append(',');
					if (column.isKey()) {
						keys.append(column.getName());
						keys.append(',');
					}
					this.columns.add( column);
				}
			keys.deleteCharAt(keys.length() - 1);
		} else {
			for (final Field f : fields)
				if (f.isAnnotationPresent(DataField.class)) {
					final Column column = new Column(f);
					columns.append(column);
					if (column.isKey())
						columns.append(" PRIMARY KEY");
					columns.append(',');
					this.columns.add( column);
				}
		}
		columns.deleteCharAt(columns.length() - 1);
		return getCreateString(tableName, columns.toString(), keys.toString(),clazz.getAnnotation(DataTypeClass.class).defineKeys());
	}

	protected static String getCreateString(final String tableName,
			final String columnsAndTypes, final String keys, final String foreign) {
		final String inside = columnsAndTypes
				+ (keys.length() > 0 ? ",primary key ( " + keys + " )" : "") + (foreign.length() > 0 ? ", " + foreign : "");

		final String string = "create table if not exists " + tableName + " ( " + inside
				+ " ); ";
		System.out.println(string);
		return string;
	}

	protected SQLiteDatabase database;
	protected ArrayList<Column> columns = new ArrayList<Column>();
	private String tableName;
	protected Class<Item> clazz;

	public Table(final Class<Item> clazz) {		
		this.clazz = clazz;
		if (!clazz.isAnnotationPresent(DataTypeClass.class))
			throw new IllegalArgumentException(clazz
					+ " is not a DataTypeClass!");
	}

	public void create(final SQLiteDatabase db) {
		database = db;
		database.execSQL(init(clazz));
	}
	public void create(final SQLiteDatabase... dbs) {
		for ( final SQLiteDatabase db : dbs) create(db);
	} 
	public void setDatabse(final SQLiteDatabase db) {
		database = db;
	}

	public long add(final Item a) {
		final ContentValues itemToValues = itemToValues(a,false);
		return database.insertOrThrow(tableName,columns.get(0).getName(),itemToValues);
	}

	public boolean insertIfNotExists(final Item i) {
		final Item item = get(i);
		if (item!=null) return false;
		replace(i);
		return true;
	}
	public long replace(final Item a) {
		Log.d(tableName,itemToValues(a,true).toString());
		return database.replaceOrThrow(tableName, null, itemToValues(a,true));
	}
	private ContentValues itemToValues(final Item a,final boolean replace) {
		final ContentValues contentValues = new ContentValues();
		for (final Column c : columns) {
			if (c.isKey() && c.isAutoIncrement() && !replace)
				continue;
			final String type = c.getType();
			if (type.equals("long"))
				contentValues.put(c.getName(), (Long) c.get(a));
			else if (type.equals("int"))
				contentValues.put(c.getName(), (Integer) c.get(a));
			else if (type.equals("boolean"))
				contentValues.put(c.getName(), (Boolean) c.get(a) ? 1 : 0);
			else if (type.equals("text"))
				contentValues.put(c.getName(), (String) c.get(a));
			else if (type.equals("real"))
				contentValues.put(c.getName(), (Float) c.get(a));
		}

		return contentValues;
	}

	public void addAll(final ArrayList<Item> items) {
		for (final Item i : items)
			add(i);
	}

	public void remove(final Item i) {
		database.delete(tableName, getEqualityWhereClause(i), null);
	}

	public ArrayList<Item> getAll() {
		ArrayList<Item> items;
		final Cursor c = database.rawQuery("SELECT * FROM " + tableName, null);
		items = getAll(c);
		c.close();
		return items;
	}
	public ArrayList<Item> getAll(final String where) {
		ArrayList<Item> items;
		final Cursor c = database.rawQuery("SELECT * FROM " + tableName + " WHERE " + where, null);
		items = getAll(c);
		c.close();
		return items;
	}

	public Item get(final String where) throws IllegalArgumentException {
		ArrayList<Item> items;
		final Cursor c = database.rawQuery("SELECT * FROM " + tableName + " WHERE " + where, null);
		items = getAll(c);
		c.close();
		if (items.size()>1) throw new IllegalArgumentException("query had more then 1 result");
		if ( items.size()<1) return null;
		return items.get(0);
	}
	protected ArrayList<Item> getAll(final Cursor c) {
		final ArrayList<Item> items = new ArrayList<Item>();
		if (c.moveToFirst()) {
			do {
				try {
					items.add(getItemFromCursor(c, clazz.newInstance()));
				} catch (final Exception e) {
					e.printStackTrace();
				}
			} while (c.moveToNext());
		}
		return items;
	}

	public Item getItemFromCursor(final Cursor c, final Item item) {
		for (final Column col : columns) {
			final String type = col.getType();
			if (type.equals("long"))
				col.set(item, c.getLong(c.getColumnIndexOrThrow(col.getName())));
			else if (type.equals("int"))
				col.set(item, c.getInt(c.getColumnIndexOrThrow(col.getName())));
			else if (type.equals("boolean"))
				col.set(item,
						c.getInt(c.getColumnIndexOrThrow(col.getName())) != 0);
			else if (type.equals("text"))
				col.set(item,
						c.getString(c.getColumnIndexOrThrow(col.getName())));
			else if (type.equals("real"))
				col.set(item,
						c.getFloat(c.getColumnIndexOrThrow(col.getName())));
		}
		return item;
	}

	public String getEqualityWhereClause(final Item i) {
		final StringBuilder stringBuilder = new StringBuilder();
		for (final Column c : columns) {
			if (c.isKey()) {
				stringBuilder.append(c.getName());
				stringBuilder.append('=');
				stringBuilder.append("'" + c.get(i) + "'");
				stringBuilder.append(" and ");
			}
		}
		final int length = stringBuilder.length();
		stringBuilder.delete(length - 4, length);
		return stringBuilder.toString();
	}

	public boolean contains(final Item i) {
		final String sql = "SELECT COUNT(*) FROM " + tableName + " WHERE "
				+ getEqualityWhereClause(i);
		final SQLiteStatement statement = database.compileStatement(sql);
		final long count = statement.simpleQueryForLong();
		return count > 0;
	}

	public Item get(final Item itemWithSameID) {
		Item item = null;
		final Cursor c = database.query(tableName, null,
				getEqualityWhereClause(itemWithSameID), null, null, null, null);
		if (c.moveToFirst())
			item = getItemFromCursor(c, itemWithSameID);
		c.close();
		return item;
	}

	public long size() {
		final String sql = "SELECT COUNT(*) FROM " + tableName;
		final SQLiteStatement statement = database.compileStatement(sql);
		final long count = statement.simpleQueryForLong();
		return count;
	}

	public void clear() {
		database.delete(tableName, null, null);
	}
	/**
	 * if added return new row id
	 * else returns -1
	 * @param i
	 * @return
	 */
	public long update(final Item i) {
		if (database.update(tableName, itemToValues(i,true), getEqualityWhereClause(i),null)<=0) 
			return add(i);
		else 
			return -1;
	}

	public String getTableName() {
		return tableName;
	}
}
